package com.aaa.opengltest;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.AttributeSet;

import java.util.List;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_BACK;
import static android.opengl.GLES20.GL_COLOR_ATTACHMENT0;
import static android.opengl.GLES20.GL_DEPTH_ATTACHMENT;
import static android.opengl.GLES20.GL_DEPTH_COMPONENT;
import static android.opengl.GLES20.GL_DEPTH_COMPONENT16;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_FRAMEBUFFER;
import static android.opengl.GLES20.GL_FRAMEBUFFER_COMPLETE;
import static android.opengl.GLES20.GL_FRONT;
import static android.opengl.GLES20.GL_NEAREST;
import static android.opengl.GLES20.GL_NONE;
import static android.opengl.GLES20.GL_RENDERBUFFER;
import static android.opengl.GLES20.GL_REPEAT;
import static android.opengl.GLES20.GL_RGBA4;
import static android.opengl.GLES20.GL_STATIC_DRAW;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE1;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_S;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_T;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_UNSIGNED_INT;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindBuffer;
import static android.opengl.GLES20.glBindFramebuffer;
import static android.opengl.GLES20.glBindRenderbuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glBufferData;
import static android.opengl.GLES20.glCheckFramebufferStatus;
import static android.opengl.GLES20.glCullFace;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glFramebufferRenderbuffer;
import static android.opengl.GLES20.glFramebufferTexture2D;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGenFramebuffers;
import static android.opengl.GLES20.glGenRenderbuffers;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glRenderbufferStorage;
import static android.opengl.GLES20.glTexImage2D;
import static android.opengl.GLES20.glTexParameterf;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUniform3f;
import static android.opengl.GLES20.glUniform3fv;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
import static android.opengl.GLES30.glBindVertexArray;
import static android.opengl.GLES30.glGenVertexArrays;

public class TestSurfaceView extends GLSurfaceView {
    int[] fbos = new int[1];
    int[] shadowVao = new int[1];
    int[] shadowVbo = new int[2];
    int[] shadowTexture = new int[1];
    int[] modelVao = new int[1];
    int[] modelVbo = new int[2];
    float[] mMatrix = new float[16];
    float[] vMatrix = new float[16];
    float[] pMatrix = new float[16];
    float[] smMatrix = new float[16];
    float[] svMatrix = new float[16];
    float[] spMatrix = new float[16];
    float[] lightSpaceMatrix = new float[16];
    float[] eye=new float[]{10,10,10};
    float[] light=new float[]{10,10,-10};
    int bgColor;
    private int shadowProgram;
    private String shadowVertCode = "#version 300 es\n" +
            "in vec3 position;\n" +
            "uniform mat4 lightSpaceMatrix;\n" +
            "uniform mat4 model;\n" +
            "void main()\n" +
            "{\n" +
            "    gl_Position = lightSpaceMatrix * model * vec4(position, 1.0f);\n" +
            "}";
    private String shadowFragCode = "#version 300 es\n" +
            "precision mediump float; \n" +
//            "out vec4 f_color; \n" +
            "void main()\n" +
            "{             \n" +
//                        "     gl_FragDepth = gl_FragCoord.z;\n" +
//                        "    f_color=vec4(gl_FragCoord.z);\n" +
            "}";
    private int modelProgram;
    private String modelVertCode = "#version 300 es\n" +
            "in vec3 position;\n" +
            "in vec3 normal;\n" +
            "in vec2 texCoords;\n" +
            "out vec3 FragPos;\n" +
            "out vec3 Normal;\n" +
            "out vec2 TexCoords;\n" +
            "out vec4 FragPosLightSpace;\n" +
            "uniform mat4 projection;\n" +
            "uniform mat4 view;\n" +
            "uniform mat4 model;\n" +
            "uniform mat4 lightSpaceMatrix;\n" +
            "void main()\n" +
            "{\n" +
            "    gl_Position = projection * view * model * vec4(position, 1.0f);\n" +
            "    FragPos = vec3(model * vec4(position, 1.0));\n" +
            "    Normal = transpose(inverse(mat3(model))) * normal;\n" +
            "    TexCoords = texCoords;\n" +
            "    FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);\n" +
            "}";
    private String modelFragCode = "#version 300 es\n" +
            "precision mediump float; \n" +
            "    out vec4 FragColor;\n" +
            "    in vec3 FragPos;\n" +
            "    in vec3 Normal;\n" +
            "    in vec2 TexCoords;\n" +
            "    in vec4 FragPosLightSpace;\n" +
            "    uniform sampler2D diffuseTexture;\n" +
            "    uniform sampler2D shadowMap;\n" +
            "    uniform vec3 lightPos;\n" +
            "    uniform vec3 viewPos;\n" +
            "    float ShadowCalculation(vec4 fragPosLightSpace)\n" +
            "    {\n" +
            "        vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;\n" +
            "        projCoords = projCoords * 0.5 + 0.5;\n" +
            "        float closestDepth = texture(shadowMap, projCoords.xy).r;\n" +
            "        float currentDepth = projCoords.z;\n" +
            "        float shadow = currentDepth > closestDepth  ? 1.0 : 0.0;\n" +
            "        return shadow;\n" +
            "    }\n" +
            "    void main()\n" +
            "    {\n" +
            "        vec3 color = texture(diffuseTexture, TexCoords).rgb;\n" +
            "        vec3 normal = normalize(Normal);\n" +
            "        vec3 lightColor = vec3(1.0);\n" +
            "        // Ambient\n" +
            "        vec3 ambient = 0.15 * color;\n" +
            "        // Diffuse\n" +
            "        vec3 lightDir = normalize(lightPos - FragPos);\n" +
            "        float diff = max(dot(lightDir, normal), 0.0);\n" +
            "        vec3 diffuse = diff * lightColor;\n" +
            "        // Specular\n" +
            "        vec3 viewDir = normalize(viewPos - FragPos);\n" +
            "        vec3 reflectDir = reflect(-lightDir, normal);\n" +
            "        float spec = 0.0;\n" +
            "        vec3 halfwayDir = normalize(lightDir + viewDir);\n" +
            "        spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);\n" +
            "        vec3 specular = spec * lightColor;\n" +
            "        // 计算阴影\n" +
            "        float shadow = ShadowCalculation(FragPosLightSpace);\n" +
            "        vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;\n" +
//            "        vec3 lighting = (ambient + 1.0 * (diffuse + specular)) * color;\n" +
            "        FragColor = vec4(lighting, 1.0f);\n" +
            "    }\n";
    private int imageProgram;
    private List<Obj3D> obj3DList;

    public TestSurfaceView(Context context) {
        super(context);
    }

    public TestSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(3);
        setRenderer(new TestRenderer());
        setRenderMode(RENDERMODE_WHEN_DIRTY);
        obj3DList = MapDataConverter.genShadowTestCubeAndFloor(context);
        bgColor = Color.argb(1, 33, 162, 254);
    }

    public void setBackgroundColor() {
        float bgRed = Color.red(bgColor) / 255f;
        float bgGreen = Color.green(bgColor) / 255f;
        float bgBlue = Color.blue(bgColor) / 255f;
        float bgAlpha = Color.alpha(bgColor) / 255f;
        GLES30.glClearColor(bgRed, bgGreen, bgBlue, bgAlpha);
        requestRender();
    }

    private void initShadowVao(List<Obj3D> modelData) {
        shadowVao = new int[modelData.size()];
        shadowVbo = new int[modelData.size()];
        glGenVertexArrays(shadowVao.length, shadowVao, 0);
        glGenBuffers(shadowVbo.length, shadowVbo, 0);
        for (int i = 0; i < modelData.size(); i++) {
            Obj3D obj3D = modelData.get(i);
            obj3D.shadowVao = shadowVao[i];

            glBindVertexArray(shadowVao[i]);
            glBindBuffer(GL_ARRAY_BUFFER, shadowVbo[i]);
            glBufferData(GL_ARRAY_BUFFER, obj3D.position.capacity() * 4, obj3D.position, GL_STATIC_DRAW);
            glVertexAttribPointer(glGetAttribLocation(shadowProgram, "position"), 3, GL_FLOAT, false, 0, 0);
            glEnableVertexAttribArray(glGetAttribLocation(shadowProgram, "position"));
            glBindVertexArray(GL_NONE);
        }
    }

    private void initModelVao(List<Obj3D> modelData) {
        modelVao = new int[modelData.size()];
        modelVbo = new int[modelData.size() * 3];

        glGenVertexArrays(modelVao.length, modelVao, 0);
        glGenBuffers(modelVbo.length, modelVbo, 0);
        for (int i = 0; i < modelData.size(); i++) {
            Obj3D obj3D = modelData.get(i);
            obj3D.modelVao = modelVao[i];
            obj3D.textureKd = createTexture(modelData.get(i).mtl.kdBitmap);
            LogUtils.i("obj3D.textureKd : " + obj3D.textureKd);
            glBindVertexArray(modelVao[i]);

            glBindBuffer(GL_ARRAY_BUFFER, modelVbo[i * 3]);
            glBufferData(GL_ARRAY_BUFFER, obj3D.position.capacity() * 4, obj3D.position, GL_STATIC_DRAW);
            glVertexAttribPointer(glGetAttribLocation(modelProgram, "position"), 3, GL_FLOAT, false, 0, 0);
            glEnableVertexAttribArray(glGetAttribLocation(modelProgram, "position"));

            glBindBuffer(GL_ARRAY_BUFFER, modelVbo[i * 3 + 1]);
            glBufferData(GL_ARRAY_BUFFER, obj3D.normal.capacity() * 4, obj3D.normal, GL_STATIC_DRAW);
            glVertexAttribPointer(glGetAttribLocation(modelProgram, "normal"), 3, GL_FLOAT, false, 0, 0);
            glEnableVertexAttribArray(glGetAttribLocation(modelProgram, "normal"));

            if (obj3D.texture != null) {
                glBindBuffer(GL_ARRAY_BUFFER, modelVbo[i * 3 + 2]);
                glBufferData(GL_ARRAY_BUFFER, obj3D.texture.capacity() * 4, obj3D.texture, GL_STATIC_DRAW);
                glVertexAttribPointer(glGetAttribLocation(modelProgram, "texCoords"), 2, GL_FLOAT, false, 0, 0);
                glEnableVertexAttribArray(glGetAttribLocation(modelProgram, "texCoords"));
            }
            glBindVertexArray(GL_NONE);
        }
    }

    private void createDepathBuffer() {
        glGenFramebuffers(1, fbos, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);

        int[] renderBuffer = new int[1];
        glGenRenderbuffers(1, renderBuffer, 0);
        glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffer[0]);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1024, 1024);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer[0]);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);

        //生成纹理ID
        glGenTextures(1, shadowTexture, 0);
        glBindTexture(GL_TEXTURE_2D, shadowTexture[0]);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, null);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowTexture[0], 0);
        glBindTexture(GL_TEXTURE_2D, 0);

        int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

        if (status != GL_FRAMEBUFFER_COMPLETE) {
            // framebuffer生成失败
            LogUtils.i(" framebuffer生成失败 " + status);
        } else {
            LogUtils.i(" framebuffer生成成功 " + fbos[0]);
        }

        // 取消绑定纹理
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    protected int createTexture(Bitmap mBitmap) {
        int[] texture = new int[1];
        if (mBitmap != null && !mBitmap.isRecycled()) {
            //生成纹理ID
            GLES30.glGenTextures(1, texture, 0);

            //绑定纹理ID
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture[0]);

            //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
            //设置放大过滤为使用纹理中坐标最接近的若干个颜色，通过加权平均算法得到需要绘制的像素颜色
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
            //设置环绕方向S，截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GL_REPEAT);
            //设置环绕方向T，截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GL_REPEAT);


            //根据以上指定的参数，生成一个2D纹理
            GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmap, 0);
            // 生成MIP贴图
            GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);

            // 数据如果已经被加载进OpenGL,则可以回收该bitmap
            //            bitmap.recycle();

            // 取消绑定纹理
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
            return texture[0];
        }
        return -1;
    }

    class TestRenderer implements Renderer {
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            GLES30.glEnable(GLES30.GL_DEPTH_TEST);
            GLES30.glEnable(GLES30.GL_CULL_FACE);
            setBackgroundColor();
            shadowProgram = ShaderUtil.createProgram(shadowVertCode, shadowFragCode);
            createDepathBuffer();
            initShadowVao(obj3DList);

            modelProgram = ShaderUtil.createProgram(modelVertCode, modelFragCode);
            initModelVao(obj3DList);

        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            GLES30.glViewport(0, 0, width, height);
            mMatrix = new float[]{
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1
            };
            Matrix.setLookAtM(vMatrix, 0, eye[0], eye[1], eye[2], 0, 0, 0, 0, 1, 0);
            Matrix.perspectiveM(pMatrix, 0, 45, (width + 0f) / height, 1f, 20);

            smMatrix = new float[]{
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1
            };
            Matrix.setLookAtM(svMatrix, 0, light[0], light[1], light[2], 0, 0, 0, 0, 1, 0);
            Matrix.orthoM(spMatrix, 0, -10, 10, -10, 10, 1, 40);

            Matrix.multiplyMM(lightSpaceMatrix, 0, spMatrix, 0, svMatrix, 0);
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            GLES30.glUseProgram(shadowProgram);
            //            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,0);
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, fbos[0]);
            glCullFace(GL_FRONT);
            glViewport(0, 0, 1024, 1024);
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
            glUniformMatrix4fv(glGetUniformLocation(shadowProgram, "model"), 1, false, smMatrix, 0);
            glUniformMatrix4fv(glGetUniformLocation(shadowProgram, "lightSpaceMatrix"), 1, false, lightSpaceMatrix, 0);
            for (int i = 0; i < obj3DList.size(); i++) {
                glBindVertexArray(obj3DList.get(i).shadowVao);
                glDrawArrays(GL_TRIANGLES, 0, obj3DList.get(i).position.limit() / 3);
                glBindVertexArray(GL_NONE);
            }
            glCullFace(GL_BACK);

            GLES30.glBindFramebuffer(GL_FRAMEBUFFER, 0);
            GLES30.glUseProgram(modelProgram);
            glViewport(0, 0, getWidth(), getHeight());
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
            glUniformMatrix4fv(glGetUniformLocation(modelProgram, "model"), 1, false, mMatrix, 0);
            glUniformMatrix4fv(glGetUniformLocation(modelProgram, "projection"), 1, false, pMatrix, 0);
            glUniformMatrix4fv(glGetUniformLocation(modelProgram, "view"), 1, false, vMatrix, 0);
            glUniformMatrix4fv(glGetUniformLocation(modelProgram, "lightSpaceMatrix"), 1, false, lightSpaceMatrix, 0);
            glUniform3fv(glGetUniformLocation(modelProgram, "lightPos"), 1, light, 0);
            glUniform3fv(glGetUniformLocation(modelProgram, "viewPos"), 1, eye, 0);


            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, shadowTexture[0]);
//            glBindTexture(GL_TEXTURE_2D, obj3DList.get(0).textureKd);//绑定纹理
            glUniform1i(glGetUniformLocation(modelProgram, "shadowMap"), 0);

            for (int i = 0; i < obj3DList.size(); i++) {
                glActiveTexture(GL_TEXTURE1);
//                glBindTexture(GL_TEXTURE_2D, obj3DList.get(i).textureKd);//绑定纹理
                glBindTexture(GL_TEXTURE_2D, shadowTexture[0]);//绑定纹理
                glUniform1i(glGetUniformLocation(modelProgram, "diffuseTexture"), 1);
                //使用VAO绘制
                glBindVertexArray(obj3DList.get(i).modelVao);
                glDrawArrays(GL_TRIANGLES, 0, obj3DList.get(i).position.limit() / 3);
                glBindVertexArray(GL_NONE);
            }

        }
    }
}
